import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:dartz/dartz.dart';
import 'package:http/http.dart' as http;
import 'package:website.core/core/domain/clients/api_client.dart';
import 'package:website.core/core/domain/config/config.dart';
import 'package:website.core/core/domain/entities/user.dart';
import 'package:website.core/core/domain/errors/exceptions.dart';
import 'package:website.core/core/domain/repositories/user_remote_repository/repository.dart';
import 'package:website.core/core/infrastructure/dtos/api/api_user_dto.dart';

part 'dtos/email_change_forbidden_response_dto.dart';

part 'dtos/password_reset_forbidden_response_dto.dart';

part 'dtos/user_getting_successful_response_dto.dart';

class UserRemoteRepositoryImpl implements UserRemoteRepository {
  const UserRemoteRepositoryImpl({
    required http.Client httpClient,
    required ApiClient apiClient,
  })  : _httpClient = httpClient,
        _apiClient = apiClient;

  final http.Client _httpClient;
  final ApiClient _apiClient;

  @override
  Future<Either<UserGettingFailure, User>> getUser() async {
    final url = Uri.parse('${Config().apiBaseUrl}/user');

    final http.Response response;

    try {
      response = await _apiClient.get(url);
    } on ServerException {
      const failure = ServerUserGettingFailure();
      return left(failure);
    } on SocketException {
      const failure = NetworkUserGettingFailure();
      return left(failure);
    } on TimeoutException {
      const failure = NetworkUserGettingFailure();
      return left(failure);
    }

    if (response.statusCode == 401) {
      const failure = UnauthorizedUserGettingFailure();
      return left(failure);
    }

    if (response.statusCode >= 500) {
      const failure = ServerUserGettingFailure();
      return left(failure);
    }

    if (response.statusCode != 200) {
      throw UnimplementedError();
    }

    final responseBody = jsonDecode(response.body) as Map<String, dynamic>;
    final successfulResponseDto = _PurchasedItemGettingSuccessfulResponseDto.fromJson(responseBody);
    final user = successfulResponseDto.userDto.toEntity();

    return right(user);
  }

  @override
  Future<Either<EmailVerificationDuringEmailChangeFailure, void>> verifyEmailDuringEmailChange({
    required String email,
  }) async {
    final payload = {
      'email': email,
    };

    final requestBody = jsonEncode(payload);

    final url = Uri.parse('${Config().apiBaseUrl}/change-email/verify-email');

    const headers = {
      'Content-Type': 'application/json',
    };

    final http.Response response;

    try {
      response = await _apiClient.post(
        url,
        body: requestBody,
        headers: headers,
      );
    } on ServerException {
      const failure = ServerEmailVerificationDuringEmailChangeFailure();
      return left(failure);
    } on SocketException {
      const failure = NetworkEmailVerificationDuringEmailChangeFailure();
      return left(failure);
    } on TimeoutException {
      const failure = NetworkEmailVerificationDuringEmailChangeFailure();
      return left(failure);
    }

    if (response.statusCode == 401) {
      const failure = UnauthorizedEmailVerificationDuringEmailChangeFailure();
      return left(failure);
    }

    if (response.statusCode == 409) {
      const failure = AlreadyExistingUserEmailVerificationDuringEmailChangeFailure();
      return left(failure);
    }

    if (response.statusCode >= 500) {
      const failure = ServerEmailVerificationDuringEmailChangeFailure();
      return left(failure);
    }

    if (response.statusCode != 200) {
      throw UnimplementedError();
    }

    return right(null);
  }

  @override
  Future<Either<EmailChangeFailure, void>> changeEmail({
    required String email,
    required String emailVerificationCode,
  }) async {
    final payload = {
      'email': email,
      'email_verification_code': emailVerificationCode,
    };

    final requestBody = jsonEncode(payload);

    final url = Uri.parse('${Config().apiBaseUrl}/change-email');

    const headers = {
      'Content-Type': 'application/json',
    };

    final http.Response response;

    try {
      response = await _apiClient.put(
        url,
        body: requestBody,
        headers: headers,
      );
    } on ServerException {
      const failure = ServerEmailChangeFailure();
      return left(failure);
    } on SocketException {
      const failure = NetworkEmailChangeFailure();
      return left(failure);
    } on TimeoutException {
      const failure = NetworkEmailChangeFailure();
      return left(failure);
    }

    if (response.statusCode == 401) {
      const failure = UnauthorizedEmailChangeFailure();
      return left(failure);
    }

    if (response.statusCode == 403) {
      final responseBody = jsonDecode(response.body) as Map<String, dynamic>;
      final responseDto = _EmailChangeForbiddenResponseDto.fromJson(responseBody);

      final failure = EmailVerificationEmailChangeFailure(
        emailVerificationCodeFound: responseDto.emailVerificationCodeFound,
        emailVerificationCodeCorrect: responseDto.emailVerificationCodeCorrect,
        emailVerificationCodeExpired: responseDto.emailVerificationCodeExpired,
        emailVerificationCodeSubmissionAttemptsOver:
            responseDto.emailVerificationCodeSubmissionAttemptsOver,
      );

      return left(failure);
    }

    if (response.statusCode == 409) {
      const failure = AlreadyExistingUserEmailChangeFailure();
      return left(failure);
    }

    if (response.statusCode >= 500) {
      const failure = ServerEmailChangeFailure();
      return left(failure);
    }

    if (response.statusCode != 200) {
      throw UnimplementedError();
    }

    return right(null);
  }

  @override
  Future<Either<PasswordChangeFailure, void>> changePassword({
    required String currentPassword,
    required String newPassword,
  }) async {
    final payload = {
      'current_password': currentPassword,
      'new_password': newPassword,
    };

    final requestBody = jsonEncode(payload);

    final url = Uri.parse('${Config().apiBaseUrl}/change-password');

    const headers = {
      'Content-Type': 'application/json',
    };

    final http.Response response;

    try {
      response = await _apiClient.put(
        url,
        body: requestBody,
        headers: headers,
      );
    } on ServerException {
      const failure = ServerPasswordChangeFailure();
      return left(failure);
    } on SocketException {
      const failure = NetworkPasswordChangeFailure();
      return left(failure);
    } on TimeoutException {
      const failure = NetworkPasswordChangeFailure();
      return left(failure);
    }

    if (response.statusCode == 401) {
      const failure = UnauthorizedPasswordChangeFailure();
      return left(failure);
    }

    if (response.statusCode == 403) {
      const failure = WrongCurrentPasswordPasswordChangeFailure();
      return left(failure);
    }

    if (response.statusCode >= 500) {
      const failure = ServerPasswordChangeFailure();
      return left(failure);
    }

    if (response.statusCode != 200) {
      throw UnimplementedError();
    }

    return right(null);
  }

  @override
  Future<Either<PasswordResetRequestFailure, void>> requestPasswordReset({
    required String email,
  }) async {
    final payload = {
      'email': email,
    };

    final requestBody = jsonEncode(payload);

    final url = Uri.parse('${Config().apiBaseUrl}/request-password-reset');

    const headers = {
      'Content-Type': 'application/json',
    };

    final http.Response response;

    try {
      response = await _httpClient.put(
        url,
        body: requestBody,
        headers: headers,
      );
    } on SocketException {
      const failure = NetworkPasswordResetRequestFailure();
      return left(failure);
    } on TimeoutException {
      const failure = NetworkPasswordResetRequestFailure();
      return left(failure);
    }

    if (response.statusCode == 404) {
      const failure = NonexistentUserPasswordResetRequestFailure();
      return left(failure);
    }

    if (response.statusCode >= 500) {
      const failure = ServerPasswordResetRequestFailure();
      return left(failure);
    }

    if (response.statusCode != 200) {
      throw UnimplementedError();
    }

    return right(null);
  }

  @override
  Future<Either<PasswordResetFailure, void>> resetPassword({
    required String newPassword,
    required String passwordResetToken,
  }) async {
    final payload = {
      'new_password': newPassword,
      'password_reset_token': passwordResetToken,
    };

    final requestBody = jsonEncode(payload);

    final url = Uri.parse('${Config().apiBaseUrl}/reset-password');

    const headers = {
      'Content-Type': 'application/json',
    };

    final http.Response response;

    try {
      response = await _httpClient.put(
        url,
        body: requestBody,
        headers: headers,
      );
    } on SocketException {
      const failure = NetworkPasswordResetFailure();
      return left(failure);
    } on TimeoutException {
      const failure = NetworkPasswordResetFailure();
      return left(failure);
    }

    if (response.statusCode == 403) {
      final responseBody = jsonDecode(response.body) as Map<String, dynamic>;
      final responseDto = _PasswordResetForbiddenResponseDto.fromJson(responseBody);

      final failure = PasswordResetTokenPasswordResetFailure(
        passwordResetTokenFound: responseDto.passwordResetTokenFound,
        passwordResetTokenExpired: responseDto.passwordResetTokenExpired,
      );

      return left(failure);
    }

    if (response.statusCode == 404) {
      const failure = NonexistentUserPasswordResetFailure();
      return left(failure);
    }

    if (response.statusCode >= 500) {
      const failure = ServerPasswordResetFailure();
      return left(failure);
    }

    if (response.statusCode != 200) {
      throw UnimplementedError();
    }

    return right(null);
  }
}
